package com.faxsun.profile.core.service;

import com.faxsun.profile.core.dao.FSCustomerDao;
import com.faxsun.profile.core.dao.FSCustomerDaoImpl;
import com.faxsun.profile.core.domain.FSCustomer;
import com.faxsun.profile.core.domain.FSCustomerImpl;
import com.faxsun.profile.core.domain.FSReceiver;
import com.faxsun.profile.core.domain.FSThirdPartyCustomer;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.broadleafcommerce.common.security.util.PasswordUtils;
import org.broadleafcommerce.common.service.GenericResponse;
import org.broadleafcommerce.common.time.SystemTime;
import org.broadleafcommerce.common.util.TransactionUtils;
import org.broadleafcommerce.core.order.domain.Order;
import org.broadleafcommerce.core.order.service.OrderService;
import org.broadleafcommerce.core.pricing.service.exception.PricingException;
import org.broadleafcommerce.profile.core.domain.Customer;
import org.broadleafcommerce.profile.core.domain.CustomerAddress;
import org.broadleafcommerce.profile.core.domain.CustomerForgotPasswordSecurityToken;
import org.broadleafcommerce.profile.core.domain.CustomerForgotPasswordSecurityTokenImpl;
import org.broadleafcommerce.profile.core.service.CustomerAddressService;
import org.broadleafcommerce.profile.core.service.CustomerService;
import org.broadleafcommerce.profile.core.service.CustomerServiceImpl;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.List;

public class FSCustomerServiceImpl extends CustomerServiceImpl implements FSCustomerService {

    protected static final Log LOG = LogFactory.getLog(FSCustomerServiceImpl.class);

    @Resource(name = "blCustomerAddressService")
    public CustomerAddressService blCustomerAddressService;

    @Resource(name = "blOrderService")
    public OrderService blOrderService;

    @Resource(name = "fsReceiverService")
    protected FSReceiverService blfsReceiverService;

    @Resource(name = "blCustomerService")
    protected CustomerService customerService;

    @Resource(name = "blThirdPartyCustomerService")
    FSThirdPartyCustomerService fscustomer3thService;

    @Override
    @Transactional(TransactionUtils.DEFAULT_TRANSACTION_MANAGER)
    public GenericResponse sendForgotPasswordNotification(String username,
                                                          String resetPasswordUrl) {
        GenericResponse response = new GenericResponse();
        Customer customer = null;

        if (username != null) {
            customer = customerDao.readCustomerByUsername(username);
        }

        checkCustomer(customer, response);

        if (!response.getHasErrors()) {
            String token = PasswordUtils
                .generateTemporaryPassword(getPasswordTokenLength());
            token = token.toLowerCase();

            Object salt = getSalt(customer, token);

            String saltString = null;
            if (salt != null) {
                saltString = Hex.encodeHexString(salt.toString().getBytes());
            }

            CustomerForgotPasswordSecurityToken fpst = new CustomerForgotPasswordSecurityTokenImpl();
            fpst.setCustomerId(customer.getId());
            fpst.setToken(passwordEncoder.encodePassword(token, saltString));
            fpst.setCreateDate(SystemTime.asDate());
            customerForgotPasswordSecurityTokenDao.saveToken(fpst);

            if (saltString != null) {
                token = token + '-' + saltString;
            }

            HashMap<String, Object> vars = new HashMap<String, Object>();
            vars.put("token", token);
            if (!StringUtils.isEmpty(resetPasswordUrl)) {
                if (resetPasswordUrl.contains("?")) {
                    resetPasswordUrl = resetPasswordUrl + "&token=" + token;
                } else {
                    resetPasswordUrl = resetPasswordUrl + "?token=" + token;
                }
            }
            vars.put("resetPasswordUrl", resetPasswordUrl);
            vars.put("customer", customer);
            emailService.sendTemplateEmail(customer.getEmailAddress(),
                getForgotPasswordEmailInfo(), vars);
        }
        return response;
    }

    @Override
    public List<FSReceiver> readReceivers(Long id) {
        FSCustomer customer = (FSCustomer) customerDao.readCustomerById(id);

        return customer.getCustomerIDCards();
    }

    @Override
    @Transactional(TransactionUtils.DEFAULT_TRANSACTION_MANAGER)
    public Customer easyRegisterCustomer(Customer customer, String password) {
        customer.setRegistered(true);

        // When unencodedPassword is set the save() will encode it
        if (customer.getId() == null) {
            customer.setId(findNextCustomerId());
        }
        customer.setUnencodedPassword(password);
        Customer retCustomer = saveCustomer(customer);
        createRegisteredCustomerRoles(retCustomer);

        notifyPostRegisterListeners(retCustomer);
        return retCustomer;
    }

    @Override
    public boolean isMobileBinded(String mobile) {

        FSCustomerDao customerDao = (FSCustomerDao) this.customerDao;

        return customerDao.isMobileBinded(mobile);
    }

    @Override
    public Customer readCustomerByUsername(String username, Boolean cacheable) {
        Customer customer = customerDao.readCustomerByUsername(username,
            cacheable);

        if (customer == null && username.contains("@")) {
            customer = customerDao.readCustomerByEmail(username);
        }

        if (customer == null) {
            // hack read by mobile and replace username with mobile
            FSCustomerDaoImpl fsCustomerDao = (FSCustomerDaoImpl) customerDao;
            customer = fsCustomerDao.readCustomerByMobile(username);

        }

        if (customer != null) {
            customer.setUsername(username);
        }

        return customer;
    }

    @Override
    @Transactional(TransactionUtils.DEFAULT_TRANSACTION_MANAGER)
    public GenericResponse resetPasswordUsingToken(String username,
                                                   String token, String password, String confirmPassword) {
        GenericResponse response = new GenericResponse();
        Customer customer = null;
        if (username != null) {
            customer = customerDao.readCustomerByUsername(username);
        }
        checkCustomer(customer, response);
        checkPassword(password, confirmPassword, response);
        CustomerForgotPasswordSecurityToken fpst = checkPasswordResetToken(
            token, response);

        if (!response.getHasErrors()) {
            if (!customer.getId().equals(fpst.getCustomerId())) {
                if (LOG.isWarnEnabled()) {
                    LOG.warn("Password reset attempt tried with mismatched customer and token "
                        + customer.getId() + ", " + token);
                }
                response.addErrorCode("invalidToken");
            }
        }

        if (!response.getHasErrors()) {
            customer.setUnencodedPassword(password);
            customer.setPasswordChangeRequired(false);
            saveCustomer(customer);
            fpst.setTokenUsedFlag(true);
            customerForgotPasswordSecurityTokenDao.saveToken(fpst);
        }

        return response;
    }

    @Override
    public List<FSThirdPartyCustomer> getThirdPartyCustomer(Customer customer) {
        return fscustomer3thService.readThirdPartyCustomerForCustomer(customer);
    }

    @Override
    public boolean isCustomer3th(String uid) {
        return fscustomer3thService.checkCustomerBy3thUid(uid);
    }


    // 未合并from账户的phone，username，Email信息。只合并了订单相关以及账户所关联的第三方的信息。
    @Override
    public void mergeCustomers(Customer from, Customer to) throws PricingException {
        // modify customer idcard: 1
        List<FSReceiver> receivers = blfsReceiverService.readByCustomer(from);
        if (receivers != null) {
            for (FSReceiver receiver : receivers) {
                receiver.setCustomer(to); // blc_idcard
                blfsReceiverService.save(receiver);
            }
        }

        // modify customer addresses: 2
        List<CustomerAddress> addresses = blCustomerAddressService.readActiveCustomerAddressesByCustomerId(from.getId());
        if (addresses != null) {
            for (CustomerAddress address : addresses) {
                address.setCustomer(to);
                blCustomerAddressService.saveCustomerAddress(address);
            }
        }

        // modify customer orders: 3
        List<Order> orders = blOrderService.findOrdersForCustomer(from);
        if (orders != null) {
            for (Order order : orders) {
                order.setCustomer(to);
                try {
                    blOrderService.save(order, false);
                } catch (PricingException e) {
                    e.printStackTrace();
                    throw e;
                }
            }
        }

        // clear role : 4
        roleDao.removeCustomerRolesByCustomerId(from.getId());

        // modify (3th party customer list) of from customer to [to customer] if there are. 5
        List<FSThirdPartyCustomer> fscustomer3th = fscustomer3thService.readThirdPartyCustomerForCustomer(from);
        if (fscustomer3th != null) {
            for (FSThirdPartyCustomer c3 : fscustomer3th) {
                c3.setCustomer(to);
            }
        }

        /***
         * 不删除用户方案，缺陷是导致加载用户可能会错误. 采用修改信息方式 避免;
         *
         * 最好的解决方案是应该修改获取用户的方式， 修正使用username，email，phone获得customer的DAO实现
         * 查询添加条件customer.deactivated = false.这样可以保留就用户的信息
         *
         ***/
        /**  Begin, remove later
         from.setEmailAddress("");
         from.setUsername("");
         ((FSCustomerImpl) from).setMobile("");
         End, remove later **/
        from.setDeactivated(true);
        customerService.saveCustomer(from);

        ((FSCustomerImpl) to).setMerged(true);
        customerService.saveCustomer(to);
    }

    @Override
    public Customer readCustomerByReferralCode(String referralCode) {
        return ((FSCustomerDao) customerDao).readCustomerByReferralCode(referralCode);
    }

    private String generateReferralCodeForCustomer(Customer customer) throws NoSuchAlgorithmException {
        return parseStringTo16MD5Str(customer.getId().toString() + "fS|3u " + customer.getFirstName());
    }

    private static String parseStringTo16MD5Str(String orgStr) throws NoSuchAlgorithmException {
        MessageDigest m = MessageDigest.getInstance("MD5");
        byte[] digest = m.digest(orgStr.getBytes());
        String hash = new BigInteger(1, digest).toString(16);
        return hash.substring(8, 24);
    }

    @Override
    public String readOrGenerateReferralCodeForCustomer(Customer customer) {
        String referralCode;
        if (customer.isAnonymous() || !(customer instanceof FSCustomer)) {
            referralCode = "faxsun";
        } else {
            FSCustomer fsCustomer = (FSCustomer) customer;
            if (StringUtils.isEmpty(fsCustomer.getReferralCode())) {
                try {
                    fsCustomer.setReferralCode(generateReferralCodeForCustomer(customer));
                    customerService.saveCustomer(fsCustomer);
                    referralCode = fsCustomer.getReferralCode();
                } catch (NoSuchAlgorithmException e) {
                    LOG.error("Error occure on generateReferralCode:" + e.getMessage());
                    referralCode = "faxsun";
                }
            } else {
                referralCode = fsCustomer.getReferralCode();
            }
        }
        return referralCode;
    }

    @Override
    public int countCustomerByEmail(String email){
    	return ((FSCustomerDao)this.customerDao).countCustomerByEmail(email);
    }
}
